﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;

namespace MupdfSharp
{
    public class PdfRender
    {
        //public static string PNGFile = "";
        private static float currentScale = 1.0f;
        /// <summary>
        /// 当前页面长宽缩放比例
        /// </summary>
        public static float CurrentScale
        {
            get { return currentScale; }
        }

        public static Bitmap RenderPage(IntPtr context, IntPtr document, IntPtr page, Rectangle pageBound, float percent)
        {
            Matrix ctm = new Matrix();
            IntPtr pix = IntPtr.Zero;
            IntPtr dev = IntPtr.Zero;
            
            int width = 600;
            int height = 800;

            float scale = percent;
            if (scale <= 0)
                scale = 1.0f;

            float zoomX = scale;
            float zoomY = scale;

            currentScale = scale;

            width = (int)(zoomX * (pageBound.Right - pageBound.Left)); // gets the size of the scaled page
            height = (int)(zoomY * (pageBound.Bottom - pageBound.Top));
            ctm.A = zoomX;
            ctm.D = zoomY; // sets the matrix as (zoomX,0,0,zoomY,0,0) 


            // creates a pixmap the same size as the width and height of the page
            pix = NativeMethods.NewPixmap(context, NativeMethods.FindDeviceColorSpace(context, "DeviceRGB"), width, height);
            // sets white color as the background color of the pixmap
            NativeMethods.ClearPixmap(context, pix, 0xFF);

            // creates a drawing device
            dev = NativeMethods.NewDrawDevice(context, pix);
            // draws the page on the device created from the pixmap
            NativeMethods.RunPage(document, page, dev, ctm, IntPtr.Zero);

            NativeMethods.FreeDevice(dev); // frees the resources consumed by the device
            dev = IntPtr.Zero;

            // creates a colorful bitmap of the same size of the pixmap
            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            var imageData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, bmp.PixelFormat);
            unsafe
            { // converts the pixmap data to Bitmap data
                byte* ptrSrc = (byte*)NativeMethods.GetSamples(context, pix); // gets the rendered data from the pixmap
                byte* ptrDest = (byte*)imageData.Scan0;
                for (int y = 0; y < height; y++)
                {
                    byte* pl = ptrDest;
                    byte* sl = ptrSrc;
                    for (int x = 0; x < width; x++)
                    {
                        //Swap these here instead of in MuPDF because most pdf images will be rgb or cmyk.
                        //Since we are going through the pixels one by one anyway swap here to save a conversion from rgb to bgr.
                        pl[2] = sl[0]; //b-r
                        pl[1] = sl[1]; //g-g
                        pl[0] = sl[2]; //r-b
                        //sl[3] is the alpha channel, we will skip it here
                        pl += 3;
                        sl += 4;
                    }
                    ptrDest += imageData.Stride;
                    ptrSrc += width * 4;
                }
            }
            NativeMethods.DropPixmap(context, pix);
            //PNGFile = System.IO.Path.GetRandomFileName() + ".png";
            //bmp.Save("pdfRender.png");
            //bmp.Save(PNGFile);
            bmp.UnlockBits(imageData);

            return bmp;
        }

        public static Bitmap RenderPage(IntPtr context, IntPtr document, IntPtr page, Rectangle pageBound, SizeF bitmapSize)
        {
            Matrix ctm = new Matrix();
            IntPtr pix = IntPtr.Zero;
            IntPtr dev = IntPtr.Zero;
            int width = 600;
            int height = 800;
            if (bitmapSize == null || bitmapSize.IsEmpty)
            {
                width = (int)(pageBound.Right - pageBound.Left); // gets the size of the page
                height = (int)(pageBound.Bottom - pageBound.Top);
                ctm.A = ctm.D = 1; // sets the matrix as the identity matrix (1,0,0,1,0,0)
            }
            else
            {
                float pageWidth = pageBound.Right - pageBound.Left;
                float pageHeight = pageBound.Bottom - pageBound.Top;
                float pageAspect = pageWidth / pageHeight;
                float awidth = 0;
                float aheight = 0;
                if (pageWidth < pageHeight)
                {
                    awidth = bitmapSize.Height * pageAspect;
                    aheight = bitmapSize.Height;
                }
                else
                {
                    awidth = bitmapSize.Height * pageAspect;
                    aheight = bitmapSize.Height;
                    if (awidth > bitmapSize.Width)
                    {
                        awidth = bitmapSize.Width;
                        aheight = awidth / pageAspect;
                    }
                }
                float zoomX = awidth / pageWidth;
                float zoomY = aheight / pageHeight;

                currentScale = zoomX;

                width = (int)awidth;// (int)(zoomX * (pageBound.Right - pageBound.Left)); // gets the size of the scaled page
                height = (int)aheight;// (int)(zoomY * (pageBound.Bottom - pageBound.Top));
                ctm.A = zoomX;
                ctm.D = zoomY; // sets the matrix as (zoomX,0,0,zoomY,0,0) 
            }

            // creates a pixmap the same size as the width and height of the page
            pix = NativeMethods.NewPixmap(context, NativeMethods.FindDeviceColorSpace(context, "DeviceRGB"), width, height);
            // sets white color as the background color of the pixmap
            NativeMethods.ClearPixmap(context, pix, 0xFF);

            // creates a drawing device
            dev = NativeMethods.NewDrawDevice(context, pix);
            // draws the page on the device created from the pixmap
            NativeMethods.RunPage(document, page, dev, ctm, IntPtr.Zero);

            NativeMethods.FreeDevice(dev); // frees the resources consumed by the device
            dev = IntPtr.Zero;

            // creates a colorful bitmap of the same size of the pixmap
            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            var imageData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, bmp.PixelFormat);
            unsafe
            { // converts the pixmap data to Bitmap data
                byte* ptrSrc = (byte*)NativeMethods.GetSamples(context, pix); // gets the rendered data from the pixmap
                byte* ptrDest = (byte*)imageData.Scan0;
                for (int y = 0; y < height; y++)
                {
                    byte* pl = ptrDest;
                    byte* sl = ptrSrc;
                    for (int x = 0; x < width; x++)
                    {
                        //Swap these here instead of in MuPDF because most pdf images will be rgb or cmyk.
                        //Since we are going through the pixels one by one anyway swap here to save a conversion from rgb to bgr.
                        pl[2] = sl[0]; //b-r
                        pl[1] = sl[1]; //g-g
                        pl[0] = sl[2]; //r-b
                        //sl[3] is the alpha channel, we will skip it here
                        pl += 3;
                        sl += 4;
                    }
                    ptrDest += imageData.Stride;
                    ptrSrc += width * 4;
                }
            }
            NativeMethods.DropPixmap(context, pix);
            // bmp.Save("pdfRender.png");
            bmp.UnlockBits(imageData);

            return bmp;
        }

        /// <summary>
        /// 渲染pdf文件，返回png文件列表
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static List<string> BeginRender(string fileName, SizeF size)
        {
            if (System.IO.File.Exists(fileName) == false)
            {
                throw new System.IO.FileNotFoundException("指定的文件不存在");
            }
            string ext = System.IO.Path.GetExtension(fileName);
            if (ext.ToLower() != ".pdf")
            {
                throw new Exception("该文件不是合法的pdf文件");
            }
            const uint FZ_STORE_DEFAULT = 256 << 20;
            IntPtr ctx = NativeMethods.NewContext(IntPtr.Zero, IntPtr.Zero, FZ_STORE_DEFAULT); // Creates the context
            IntPtr stm = NativeMethods.OpenFile(ctx, fileName); // opens file test.pdf as a stream
            IntPtr doc = NativeMethods.OpenDocumentStream(ctx, ".pdf", stm); // opens the document
            int pn = NativeMethods.CountPages(doc); // gets the number of pages in the document

            List<string> pagesList = new List<string>(pn);
            string pdfname = System.IO.Path.GetFileNameWithoutExtension(fileName);
            string pdfdir = System.IO.Path.GetDirectoryName(fileName);
            if (!System.IO.Directory.Exists(pdfdir))
                System.IO.Directory.CreateDirectory(pdfdir);
            pdfdir = System.IO.Path.Combine(pdfdir, pdfname);
            if (!System.IO.Directory.Exists(pdfdir))
                System.IO.Directory.CreateDirectory(pdfdir);

            for (int i = 0; i < pn; i++)
            {
                string bmpFileName = System.IO.Path.Combine(pdfdir, (i + 1) + ".png");
                // iterate through each pages
                Console.WriteLine("Rendering page " + (i + 1));
                IntPtr p = NativeMethods.LoadPage(doc, i); // loads the page (first page number is 0)
                Rectangle b = NativeMethods.BoundPage(doc, p); // gets the page size
                using (var bmp = RenderPage(ctx, doc, p, b, size))
                { // renders the page and converts the result to Bitmap
                    Console.WriteLine("Saving picture: " + bmpFileName);
                    bmp.Save(bmpFileName); // saves the bitmap to a file
                }
                pagesList.Add(bmpFileName);
                NativeMethods.FreePage(doc, p); // releases the resources consumed by the page
            }

            NativeMethods.CloseDocument(doc); // releases the resources
            NativeMethods.CloseStream(stm);
            NativeMethods.FreeContext(ctx);

            Console.WriteLine("Program finished. Press any key to quit.");
            return pagesList;
        }
    }
}
